﻿using MiniFox.Infrastructure.Attributes;
using MiniFox.Resources;
using MiniFox.Utility;
using Newtonsoft.Json;
using System.Linq.Expressions;
using System.Reflection;

namespace MiniFox.Infrastructure
{
    /// <summary>
    /// 查询模型基础类型
    /// </summary>
    /// <example>
    ///定义Parameters
    ///public class Parameters
    ///{
    ///    [TargetProperty("user_id", Oper = QueryOperation.Equal)]
    ///    public string UserID { get; set; }
    ///    [TargetProperty("user_name", Oper = QueryOperation.Contain)]
    ///    public string UserName { get; set; }
    ///}
    /// 
    /// //由http post请求得到QueryUser对象
    /// QueryUser query = ........;
    /// //调用实例方法得到 lambda 表达式 ， 表达式的生成取决于继承类型中定义了 TargetProperty 特性的属性
    /// Func&lt;User, bool&gt; func = query.Filter.GetLambdaExpression&lt;User&gt;();
    /// //func传入到Where方法
    /// DbContext.User.Where(func);
    /// </example>
    [JsonObject]
    public class QueryObject<T> : IQueryObject
    {
        bool _sortDesc;
        string _sortField;
        Dictionary<string, bool> _sortFields;
        /// <summary>
        /// 
        /// </summary>
        public QueryObject()
        {
        }
        /// <summary>
        /// 查询参数
        /// </summary>
        [JsonProperty("parameters")]
        public T Parameters
        {
            get;
            set;
        }
        /// <summary>
        /// 组合排序，key为字段名，value为排序方式  false:升序/true:降序
        /// </summary>
        [JsonProperty("sortFields")]
        public Dictionary<string, bool> SortFields
        {
            get
            {

                if (_sortFields == null)
                    _sortFields = new Dictionary<string, bool>();

                if (!string.IsNullOrWhiteSpace(SortField) && !_sortFields.ContainsKey(SortField))
                    _sortFields.Add(SortField, SortDesc);
                return _sortFields;
            }
        }

        /// <summary>
        /// 默认排序字段
        /// </summary>
        [JsonProperty("sortField")]
        public string SortField
        {
            get
            {
                return _sortField;
            }
            set
            {
                if (!string.IsNullOrEmpty(value))
                {
                    _sortField = value;
                }
            }
        }

        /// <summary>
        /// 默认排序方式
        /// </summary>
        [JsonProperty("sortDesc")]
        public bool SortDesc
        {
            get { return _sortDesc; }
            set { _sortDesc = value; }
        }


        /// <summary>
        /// 通过循环检查参数对象所有公有属性，通过提取标记为TargetPropertyAttribute特性的属性，创建参数集合
        /// </summary>
        /// <returns>参数集合（字典）</returns>
        public IDictionary<string, object> GetParametersDictionary()
        {
            return GetParametersDictionary(false);
        }
        /// <summary>
        /// 通过循环检查参数对象所有公有属性，通过提取标记为TargetPropertyAttribute特性的属性，创建参数集合
        /// </summary>
        /// <param name="ignoreTarget">是否忽略未标记TargetPropertyAttribute特性的属性（默认为false），当该值为false时，检查参数对象属性未标记TargetPropertyAttribute或TargetPropertyAttribute.PropertyName为空时，将使用属性名代替参数名</param>
        /// <returns>参数集合（字典）</returns>
        public virtual IDictionary<string, object> GetParametersDictionary(bool ignoreTarget)
        {
            Dictionary<string, object> paraDict = new Dictionary<string, object>();
            var parameters = this.Parameters;
            Type modelType = parameters.GetType();
            PropertyInfo[] properties = modelType.GetProperties();

            foreach (PropertyInfo property in properties)
            {
                TargetPropertyAttribute attr = property.GetCustomAttribute<TargetPropertyAttribute>();
                if (ignoreTarget && attr == null) //忽略未标记TargetPropertyAttribute特性的属性
                    continue;
                string name = attr == null || string.IsNullOrWhiteSpace(attr.PropertyName) ? property.Name : attr.PropertyName;

                object val = property.GetValue(parameters);
                if (val == null)
                    continue;
                if (val is string && val.ToString() == string.Empty)
                    continue;
                if (val is Array && ((Array)val).Length <= 0)//为空数组
                    continue;

                paraDict.Add(name, val);
            }
            return paraDict;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public virtual string GetQueryExpression()
        {
            var parameters = this.Parameters;
            Type modelType = parameters.GetType();
            PropertyInfo[] properties = modelType.GetProperties();
            List<string> conditionList = new List<string>();
            foreach (PropertyInfo property in properties)
            {
                TargetPropertyAttribute attr = property.GetCustomAttribute<TargetPropertyAttribute>();
                if (attr == null) //忽略未标记TargetPropertyAttribute特性的属性
                    continue;

                object val = property.GetValue(parameters);
                if (attr.IngoreNull && val == null)
                    continue;
                //if (val is string && val.ToString() == string.Empty)
                //    continue;
                if (val is Array && ((Array)val).Length <= 0)//为空数组
                    continue;
                if (string.IsNullOrEmpty(attr.PropertyName))
                {
                    attr.PropertyName = property.Name;
                }
                var condition = CreateExpression(attr, val);
                conditionList.Add(condition);
            }

            return string.Join(" AND ", conditionList.ToArray());
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="attr"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        internal static string CreateExpression(TargetPropertyAttribute attr, object val)
        {
            string[] propertyNames = attr.PropertyName.Split(',');
            List<string> expressions = new List<string>();
            foreach (string propertyName in propertyNames)
            {
                if (string.IsNullOrWhiteSpace(propertyName))
                    continue;

                string expression = null;

                if (val is Array) //数组
                {
                    if (val is Array array && array.Length > 0)
                    {
                        var arrayExp = CreateArrayExpression(array);

                        switch (attr.Operation)
                        {
                            case QueryOperation.Equal://完全匹配表达式
                            case QueryOperation.Contain://模糊匹配表达式
                            case QueryOperation.StartWith:
                            case QueryOperation.EndWith:
                                expression = $"{propertyName} IN ({arrayExp})";
                                break;
                            case QueryOperation.NotEqual:
                            case QueryOperation.NotContain://模糊匹配表达式
                                expression = $"{propertyName} NOT IN ({arrayExp})";
                                break;
                            default:
                                expression = string.Empty;
                                break;
                        }
                    }

                }
                else
                {
                    string valExp = CreateValueExpression(val);
                    switch (attr.Operation)
                    {
                        case QueryOperation.Equal:
                            expression = $"{propertyName} = {valExp}";
                            break;
                        case QueryOperation.NotEqual:
                            expression = $"{propertyName} != {valExp}";
                            break;
                        case QueryOperation.GreaterThan:
                            expression = $"{propertyName} > {valExp}";
                            break;
                        case QueryOperation.GreaterThanOrEqual:
                            expression = $"{propertyName} >= {valExp}";
                            break;
                        case QueryOperation.LessThan:
                            expression = $"{propertyName} < {valExp}";
                            break;
                        case QueryOperation.LessThanOrEqual:
                            expression = $"{propertyName} <= {valExp}";
                            break;
                        case QueryOperation.Contain:
                            expression = $"{propertyName} LIKE '%' + {valExp} + '%'";
                            break;
                        case QueryOperation.StartWith:
                            expression = $"{propertyName} LIKE {valExp} + '%'";
                            break;
                        case QueryOperation.EndWith:
                            expression = $"{propertyName} LIKE '%' + {valExp}";
                            break;
                        case QueryOperation.NotContain:
                            expression = $"{propertyName} NOT LIKE '%' + {valExp} + '%'";
                            break;
                        case QueryOperation.NotStartWith:
                            expression = $"{propertyName} NOT LIKE {valExp} + '%'";
                            break;
                        case QueryOperation.NotEndWith:
                            expression = $"{propertyName} NOT LIKE '%' + {valExp}";
                            break;
                    }


                }
                expressions.Add(expression);

            }
            return string.Join(" OR ", expressions);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="array"></param>
        /// <returns></returns>
        internal static string CreateArrayExpression(Array array)
        {
            List<string> valExpressions = new List<string>();
            foreach (var val in array)
            {
                string valExp = CreateValueExpression(val);
                valExpressions.Add(valExp);
            }
            return string.Join(",", valExpressions);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="val"></param>
        /// <returns></returns>
        internal static string CreateValueExpression(object val)
        {
            var type = val.GetType();
            TypeCode typeCode = Type.GetTypeCode(type);
            string valExpression;
            switch (typeCode)
            {
                case TypeCode.Boolean:
                    valExpression = Convert.ToByte(val).ToString();
                    break;
                case TypeCode.Byte:
                case TypeCode.SByte:
                case TypeCode.Int16:
                case TypeCode.UInt16:
                case TypeCode.Int32:
                case TypeCode.UInt32:
                case TypeCode.Int64:
                case TypeCode.UInt64:
                case TypeCode.Single:
                case TypeCode.Double:
                case TypeCode.Decimal:
                    valExpression = val.ToString();
                    break;
                case TypeCode.DateTime:
                    valExpression = $"'{string.Format($"{{0:{GlobalStandardFormat.SIMPLE_DATETIME_FORMAT}}}", val)}'";
                    break;
                case TypeCode.Char:
                case TypeCode.String:
                    valExpression = $"'{val}'";
                    break;
                case TypeCode.Empty:
                case TypeCode.DBNull:
                    valExpression = $"NULL";
                    break;
                case TypeCode.Object:
                    val = val.ToJsonString();
                    valExpression = $"'{val}'";
                    break;
                default:
                    valExpression = string.Empty;
                    break;
            }
            return valExpression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public virtual Expression<Func<TEntity, bool>> GetQueryExpression<TEntity>()
        {
            Type genericType = typeof(TEntity);
            var parameters = this.Parameters;
            Type modelType = parameters.GetType();
            PropertyInfo[] properties = modelType.GetProperties();
            Expression bodyExpression = null;
            ParameterExpression parameterExp = Expression.Parameter(genericType);
            foreach (PropertyInfo property in properties)
            {
                TargetPropertyAttribute attr = Attribute.GetCustomAttribute(property, typeof(TargetPropertyAttribute)) as TargetPropertyAttribute;
                if (attr == null)
                    continue;

                object val = property.GetValue(parameters);
                if (attr.IngoreNull && val == null)
                    continue;
                if (val is string && val.ToString() == string.Empty)
                    continue;
                if (val is Array && ((Array)val).Length <= 0)//为空数组
                    continue;
                if (string.IsNullOrEmpty(attr.PropertyName))
                {
                    attr.PropertyName = property.Name;
                }
                Expression bExp = CreateExpression(attr, parameterExp, val);
                if (bodyExpression == null)
                {
                    bodyExpression = bExp;
                }
                else
                {
                    bodyExpression = Expression.And(bodyExpression, bExp);
                }
            }
            if (bodyExpression == null)
                return null;

            return Expression.Lambda<Func<TEntity, bool>>(bodyExpression, parameterExp);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="attr"></param>
        /// <param name="parameterExp"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        internal static Expression CreateExpression(TargetPropertyAttribute attr, ParameterExpression parameterExp, object val)
        {

            string[] properties = attr.PropertyName.Split(',');
            Expression expression = null;
            foreach (string property in properties)
            {
                if (string.IsNullOrWhiteSpace(property))
                    continue;
                Expression propertyExp = parameterExp;

                string[] propertyNames = property.Split('.');
                foreach (string propertyName in propertyNames)
                {
                    if (string.IsNullOrWhiteSpace(propertyName))
                        continue;
                    var p = propertyExp.Type.GetProperty(propertyName.Trim(), BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                    if (p != null)
                        propertyExp = Expression.PropertyOrField(propertyExp, p.Name);

                }

                Expression bExp = null;

                if (val is Array) //数组
                {
                    Array array = val as Array;
                    Expression orExp = null;
                    if (array != null)
                    {
                        //为数组中每一项值作表达式，并用OR串连
                        foreach (object v in array)
                        {
                            //提取值作常量表达式
                            ConstantExpression constExp = Expression.Constant(v, propertyExp.Type);
                            switch (attr.Operation)
                            {
                                case QueryOperation.Equal://完全匹配表达式
                                    orExp = Expression.Equal(propertyExp, constExp);
                                    break;
                                case QueryOperation.NotEqual:
                                    orExp = Expression.NotEqual(propertyExp, constExp);
                                    break;
                                case QueryOperation.Contain://模糊匹配表达式
                                    orExp = Expression.Call(propertyExp, typeof(string).GetMethod("Contains"), constExp);
                                    break;
                                case QueryOperation.NotContain://模糊匹配表达式
                                    orExp = Expression.Not(Expression.Call(propertyExp, typeof(string).GetMethod("Contains"), constExp));
                                    break;
                                default:
                                    break;
                            }
                            if (bExp == null)
                            {
                                bExp = orExp;
                            }
                            else
                            {
                                switch (attr.Operation)
                                {
                                    case QueryOperation.Equal:
                                    case QueryOperation.Contain:
                                    case QueryOperation.StartWith:
                                    case QueryOperation.EndWith:
                                        bExp = Expression.Or(bExp, orExp);
                                        break;
                                    case QueryOperation.NotEqual:
                                    case QueryOperation.NotContain:
                                        bExp = Expression.And(bExp, orExp);
                                        break;
                                }
                            }
                        }
                    }

                }
                else
                {
                    ConstantExpression constValExp = Expression.Constant(val, propertyExp.Type);
                    //ConstantExpression constEnumExp = Expression.Constant(attr.StringComparison);
                    var method = typeof(string).GetMethod("Contains", new Type[] { typeof(string) });

                    switch (attr.Operation)
                    {
                        case QueryOperation.Equal:
                            bExp = Expression.Equal(propertyExp, constValExp);
                            break;
                        case QueryOperation.NotEqual:
                            bExp = Expression.NotEqual(propertyExp, constValExp);
                            break;
                        case QueryOperation.GreaterThan:
                            if (val == null)
                                throw new InvalidOperationException(CommonMessages.MSG_NullValueUnComparable);
                            bExp = Expression.GreaterThan(propertyExp, constValExp);
                            break;
                        case QueryOperation.GreaterThanOrEqual:
                            if (val == null)
                                throw new InvalidOperationException(CommonMessages.MSG_NullValueUnComparable);
                            bExp = Expression.GreaterThanOrEqual(propertyExp, constValExp);
                            break;
                        case QueryOperation.LessThan:
                            if (val == null)
                                throw new InvalidOperationException(CommonMessages.MSG_NullValueUnComparable);
                            bExp = Expression.LessThan(propertyExp, constValExp);
                            break;
                        case QueryOperation.LessThanOrEqual:
                            if (val == null)
                                throw new InvalidOperationException(CommonMessages.MSG_NullValueUnComparable);
                            bExp = Expression.LessThanOrEqual(propertyExp, constValExp);
                            break;
                        case QueryOperation.Contain:
                            if (val == null)
                                throw new InvalidOperationException(CommonMessages.MSG_NullValueUnComparable);
                            bExp = Expression.Call(propertyExp, method, constValExp);
                            break;
                        case QueryOperation.NotContain:
                            if (val == null)
                                throw new InvalidOperationException(CommonMessages.MSG_NullValueUnComparable);
                            bExp = Expression.Not(Expression.Call(propertyExp, method, constValExp));
                            break;
                        case QueryOperation.StartWith:
                            if (val == null)
                                throw new InvalidOperationException(CommonMessages.MSG_NullValueUnComparable);
                            bExp = Expression.Call(propertyExp, typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) }), constValExp);
                            break;
                        case QueryOperation.EndWith:
                            if (val == null)
                                throw new InvalidOperationException(CommonMessages.MSG_NullValueUnComparable);
                            bExp = Expression.Call(propertyExp, typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) }), constValExp);
                            break;
                        default:
                            break;
                    }

                }
                if (expression == null)
                {
                    expression = bExp;
                }
                else
                {
                    expression = Expression.Or(expression, bExp);
                }
            }
            return expression;
        }
    }
    /// <summary>
    /// 
    /// </summary>
    public class QueryObject : QueryObject<object>
    {

    }
}
